package com.baidu.iit.pxp.behavior;

import com.baidu.iit.pvm.ActivityException;
import com.baidu.iit.pvm.PvmActivity;
import com.baidu.iit.pvm.PvmTransition;
import com.baidu.iit.pvm.delegate.ActivityExecution;
import com.baidu.iit.pvm.delegate.Condition;
import com.baidu.iit.pxp.entity.ExecutionEntity;
import com.baidu.iit.pxp.handler.GatewayActivityBehavior;
import com.baidu.iit.pxp.parser.BpmnParse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * User: huangweili
 * Date: 14-4-27
 * Time: 上午9:19
 */
public class InclusiveGatewayActivityBehavior extends GatewayActivityBehavior {

    private static final long serialVersionUID = 1L;

    private static Logger logger = LoggerFactory.getLogger(InclusiveGatewayActivityBehavior.class.getName());

    public void execute(ActivityExecution execution) throws Exception {

        execution.inactivate();
        lockConcurrentRoot(execution);

        PvmActivity activity = execution.getActivity();
        if (!activeConcurrentExecutionsExist(execution)) {

            if (logger.isDebugEnabled()) {
                logger.debug("inclusive gateway '{}' activates", activity.getId());
            }

            List<ActivityExecution> joinedExecutions = execution
                    .findInactiveConcurrentExecutions(activity);
            String defaultSequenceFlow = (String) execution.getActivity()
                    .getProperty("default");
            List<PvmTransition> transitionsToTake = new ArrayList<PvmTransition>();

            for (PvmTransition outgoingTransition : execution.getActivity()
                    .getOutgoingTransitions()) {
                if (defaultSequenceFlow == null
                        || !outgoingTransition.getId().equals(defaultSequenceFlow)) {
                    Condition condition = (Condition) outgoingTransition
                            .getProperty(BpmnParse.PROPERTYNAME_CONDITION);
                    if (condition == null || condition.evaluate(execution)) {
                        transitionsToTake.add(outgoingTransition);
                    }
                }
            }

            if (transitionsToTake.size() > 0) {
                execution.takeAll(transitionsToTake, joinedExecutions);

            } else {

                if (defaultSequenceFlow != null) {
                    PvmTransition defaultTransition = execution.getActivity()
                            .findOutgoingTransition(defaultSequenceFlow);
                    if (defaultTransition != null) {
                        execution.take(defaultTransition);
                    } else {
                        throw new ActivityException("Default sequence flow '"
                                + defaultSequenceFlow + "' could not be not found");
                    }
                } else {
                    throw new ActivityException(
                            "No outgoing sequence flow of the inclusive gateway '"
                                    + execution.getActivity().getId()
                                    + "' could be selected for continuing the process"
                    );
                }
            }

        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Inclusive gateway '{}' does not activate", activity.getId());
            }
        }
    }

    List<? extends ActivityExecution> getLeaveExecutions(ActivityExecution parent) {
        List<ActivityExecution> executionlist = new ArrayList<ActivityExecution>();
        List<? extends ActivityExecution> subExecutions = parent.getExecutions();
        if (subExecutions.size() == 0) {
            executionlist.add(parent);
        } else {
            for (ActivityExecution concurrentExecution : subExecutions) {
                executionlist.addAll(getLeaveExecutions(concurrentExecution));
            }
        }
        return executionlist;
    }

    public boolean activeConcurrentExecutionsExist(ActivityExecution execution) {
        PvmActivity activity = execution.getActivity();
        if (execution.isConcurrent()) {
            for (ActivityExecution concurrentExecution : getLeaveExecutions(execution.getParent())) {
                if (concurrentExecution.isActive() && concurrentExecution.getActivity() != activity) {
                    // TODO: when is transitionBeingTaken cleared? Should we clear it?
                    boolean reachable = false;
                    PvmTransition pvmTransition = ((ExecutionEntity) concurrentExecution).getTransitionBeingTaken();
                    if (pvmTransition != null) {
                        reachable = isReachable(pvmTransition.getDestination(), activity, new HashSet<PvmActivity>());
                    } else {
                        reachable = isReachable(concurrentExecution.getActivity(), activity, new HashSet<PvmActivity>());
                    }

                    if (reachable) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("an active concurrent execution found: '{}'", concurrentExecution.getActivity());
                        }
                        return true;
                    }
                }
            }
        } else if (execution.isActive()) {
            if (logger.isDebugEnabled()) {
                logger.debug("an active concurrent execution found: '{}'",
                        execution.getActivity());
            }
            return true;
        }

        return false;
    }

    protected boolean isReachable(PvmActivity srcActivity,
                                  PvmActivity targetActivity, Set<PvmActivity> visitedActivities) {

        if (srcActivity.getOutgoingTransitions().size() == 0) {
            visitedActivities.add(srcActivity);
            if (srcActivity.getParent() == null || !(srcActivity.getParent() instanceof PvmActivity)) {
                return false;
            }
            srcActivity = (PvmActivity) srcActivity.getParent();
        }

        if (srcActivity.equals(targetActivity)) {
            return true;
        }

        visitedActivities.add(srcActivity);

        List<PvmTransition> transitionList = srcActivity.getOutgoingTransitions();
        if (transitionList != null && transitionList.size() > 0) {
            for (PvmTransition pvmTransition : transitionList) {
                PvmActivity destinationActivity = pvmTransition.getDestination();
                if (destinationActivity != null && !visitedActivities.contains(destinationActivity)) {
                    boolean reachable = isReachable(destinationActivity, targetActivity,
                            visitedActivities);


                    if (reachable) {
                        return true;
                    }

                }
            }
        }
        return false;
    }

}
